﻿#include "NPCObject.h"
#include "NPC/NPCScript.h"
#include "../Settings.h"
#include "../../Shared/ServerPackets.h"
#include "../../Shared/Globals.h"
#include "../../Shared/Functions/Functions.h"
#include "../MirEnvir/Map.h"

using namespace Server::MirDatabase;
using namespace Server::MirEnvir;
using namespace Server::MirObjects;
namespace S = ServerPackets;

namespace Server::MirObjects
{

    ObjectType NPCObject::getRace() const
    {
        return ObjectType::Merchant;
    }

    NPCObject *NPCObject::Get(unsigned int objectID)
    {
        if (objectID == 0)
        {
            return nullptr;
        }

        auto obj = getEnvir()->NPCs.SingleOrDefault([&] (std::any x)
        {
            return x->ObjectID == objectID;
        });

        if (obj != nullptr && dynamic_cast<NPCObject*>(obj) != nullptr)
        {
            return dynamic_cast<NPCObject*>(obj);
        }

        return nullptr;
    }

    int NPCObject::getScriptID() const
    {
        return ScriptID;
    }

    void NPCObject::setScriptID(int value)
    {
        ScriptID = value;
    }

    NPCObject::NPCObject(NPCInfo *info)
    {
        Info = info;
        NameColour = Color::Lime;

        setDirection(static_cast<MirDirection>(getEnvir()->Random->Next(3)));
        TurnTime = getEnvir()->getTime() + getEnvir()->Random->Next(100);

        getEnvir()->NPCs.push_back(this);

        Spawned();
        LoadScript();
    }

    void NPCObject::LoadScript()
    {
        auto script = NPCScript::GetOrAdd(ObjectID, Info->FileName, NPCScriptType::Normal);

        setScriptID(script->ScriptID);
    }

    void NPCObject::ProcessGoods(bool clear)
    {
        if (!Settings::GoodsOn)
        {
            return;
        }

        auto script = NPCScript::Get(getScriptID());

        std::vector<UserItem*> deleteList;

        for (auto playerGoods : BuyBack)
        {
            std::vector<UserItem*> items = playerGoods.Value;

            for (int i = 0; i < items.size(); i++)
            {
                UserItem *item = items[i];

                if (DateTime::Compare(item->BuybackExpiryDate.AddMinutes(Settings::GoodsBuyBackTime), getEnvir()->getNow()) <= 0 || clear)
                {
                    deleteList.push_back(BuyBack[playerGoods.Key][i]);

                    if (script->UsedTypes.size() != 0 && !std::find(script->UsedTypes.begin(), script->UsedTypes.end(), item->Info->Type) != script->UsedTypes.end())
                    {
                        continue;
                    }

                    auto multiCount = UsedGoods.size()([&] (std::any x)
                    {
                        return x::Info->Index == item->Info->Index;
                    });

                    if (multiCount >= Settings::GoodsMaxStored)
                    {
                        UserItem *nonAddedItem = UsedGoods.FirstOrDefault([&] (std::any e)
                        {
                            return e->IsAdded == false;
                        });

                        if (nonAddedItem != nullptr)
                        {
                            UsedGoods.Remove(nonAddedItem);
                        }
                        else
                        {
                            UsedGoods.erase(UsedGoods.begin());
                        }
                    }

                    UsedGoods.push_back(item);
                    NeedSave = true;
                }
            }

            for (int i = 0; i < deleteList.size(); i++)
            {
                BuyBack[playerGoods.Key].Remove(deleteList[i]);
            }
        }
    }

    void NPCObject::Process(DelayedAction *action)
    {
        throw NotSupportedException();
    }

    bool NPCObject::IsAttackTarget(PlayerObject *attacker)
    {
        return false;
    }

    bool NPCObject::IsFriendlyTarget(PlayerObject *ally)
    {
        throw NotSupportedException();
    }

    bool NPCObject::IsFriendlyTarget(MonsterObject *ally)
    {
        throw NotSupportedException();
    }

    bool NPCObject::IsAttackTarget(MonsterObject *attacker)
    {
        return false;
    }

    Buff *NPCObject::AddBuff(BuffType type, MapObject *owner, int duration, Stats *stat, bool visible, bool infinite, bool stackable, bool refreshStats, std::vector<int> &values)
    {
        throw NotSupportedException();
    }

    int NPCObject::Attacked(PlayerObject *attacker, int damage, DefenceType type, bool damageWeapon)
    {
        throw NotSupportedException();
    }

    int NPCObject::Attacked(MonsterObject *attacker, int damage, DefenceType type)
    {
        throw NotSupportedException();
    }

    int NPCObject::Struck(int damage, DefenceType type)
    {
        throw NotSupportedException();
    }

    void NPCObject::SendHealth(PlayerObject *player)
    {
        throw NotSupportedException();
    }

    void NPCObject::Die()
    {
        throw NotSupportedException();
    }

    int NPCObject::Pushed(MapObject *pusher, MirDirection dir, int distance)
    {
        throw NotSupportedException();
    }

    unsigned short NPCObject::getLevel() const
    {
        throw NotSupportedException();
    }

    void NPCObject::setLevel(unsigned short value)
    {
        throw NotSupportedException();
    }

    void NPCObject::ReceiveChat(const std::string &text, ChatType type)
    {
        throw NotSupportedException();
    }

    void NPCObject::Turn(MirDirection dir)
    {
        setDirection(dir);

        S::ObjectTurn *tempVar = new S::ObjectTurn();
        tempVar->ObjectID = ObjectID;
        tempVar->Direction = getDirection();
        tempVar->Location = getCurrentLocation();
        Broadcast(tempVar);

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar' statement was not added since tempVar was passed to a method or constructor. Handle memory management manually.
    }

    void NPCObject::Process()
    {
        MapObject::Process();

        if (getEnvir()->getTime() > TurnTime)
        {
            TurnTime = getEnvir()->getTime() + TurnDelay;
            Turn(static_cast<MirDirection>(getEnvir()->Random->Next(3)));
        }

        if (getEnvir()->getTime() > UsedGoodsTime)
        {
            UsedGoodsTime = getEnvir()->getTime() + (Settings::Minute * Settings::GoodsBuyBackTime);
            ProcessGoods();
        }

        if (getEnvir()->getTime() > VisTime)
        {
            VisTime = getEnvir()->getTime() + (Settings::Minute);

//C# TO C++ CONVERTER TODO TASK: There is no C++ equivalent to 'ToString':
            if (Info->DayofWeek != "" && Info->DayofWeek != DateTime::Now.DayOfWeek.ToString())
            {
                if (Visible)
                {
                    Hide();
                }
            }
            else
            {
                int StartTime = ((Info->HourStart * 60) + Info->MinuteStart);
                int FinishTime = ((Info->HourEnd * 60) + Info->MinuteEnd);
                int CurrentTime = ((DateTime::Now.Hour * 60) + DateTime::Now.Minute);

                if (Info->TimeVisible)
                {
                    if (StartTime > CurrentTime || FinishTime <= CurrentTime)
                    {
                        if (Visible)
                        {
                            Hide();
                        }
                    }
                    else if (StartTime <= CurrentTime && FinishTime > CurrentTime)
                    {
                        if (!Visible)
                        {
                            Show();
                        }
                    }
                }
            }
        }

        if (Speech.size() > 0 && getEnvir()->getTime() > SpeechTime)
        {
            auto nearby = FindNearby(4);

            SpeechTime = getEnvir()->getTime() + (SpeechDelay * (nearby ? getEnvir()->Random->Next(1, 13) : 1));

            if (nearby)
            {
                auto maxWeight = Speech.Max([&] (std::any x)
                {
                    x::Weight;
                });

                auto speech = Speech.OrderBy([&] (std::any x)
                {
                    x::GetWeight(getEnvir()->Random, maxWeight);
                }).Last();

                S::ObjectChat *tempVar = new S::ObjectChat();
                tempVar->ObjectID = this->ObjectID;
                tempVar->Text = StringHelper::formatSimple("{0}:{1}", StringHelper::split(Info->Name, '_')[0], speech->Message);
                tempVar->Type = ChatType::Normal;
                Broadcast(tempVar);

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar' statement was not added since tempVar was passed to a method or constructor. Handle memory management manually.
            }
        }
    }

    void NPCObject::SetOperateTime()
    {
        long long time = getEnvir()->getTime() + 2000;

        if (TurnTime < time && TurnTime > getEnvir()->getTime())
        {
            time = TurnTime;
        }

        if (OwnerTime < time && OwnerTime > getEnvir()->getTime())
        {
            time = OwnerTime;
        }

        if (ExpireTime < time && ExpireTime > getEnvir()->getTime())
        {
            time = ExpireTime;
        }

        if (PKPointTime < time && PKPointTime > getEnvir()->getTime())
        {
            time = PKPointTime;
        }

        if (LastHitTime < time && LastHitTime > getEnvir()->getTime())
        {
            time = LastHitTime;
        }

        if (EXPOwnerTime < time && EXPOwnerTime > getEnvir()->getTime())
        {
            time = EXPOwnerTime;
        }

        if (BrownTime < time && BrownTime > getEnvir()->getTime())
        {
            time = BrownTime;
        }

        for (int i = 0; i < ActionList.size(); i++)
        {
            if (ActionList[i]->Time >= time && ActionList[i]->Time > getEnvir()->getTime())
            {
                continue;
            }
            time = ActionList[i]->Time;
        }

        for (int i = 0; i < PoisonList.size(); i++)
        {
            if (PoisonList[i]->TickTime >= time && PoisonList[i]->TickTime > getEnvir()->getTime())
            {
                continue;
            }
            time = PoisonList[i]->TickTime;
        }

        for (int i = 0; i < Buffs.size(); i++)
        {
            if (Buffs[i]->ExpireTime >= time && Buffs[i]->ExpireTime > getEnvir()->getTime())
            {
                continue;
            }
            time = Buffs[i]->ExpireTime;
        }


        if (OperateTime <= getEnvir()->getTime() || time < OperateTime)
        {
            OperateTime = time;
        }
    }

    void NPCObject::Hide()
    {
        S::ObjectRemove *tempVar = new S::ObjectRemove(), *CurrentLocation;
        tempVar->ObjectID = ObjectID;
        getCurrentMap()->Broadcast(tempVar, CurrentLocation);
        Visible = false;

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar' statement was not added since tempVar was passed to a method or constructor. Handle memory management manually.
    }

    void NPCObject::Show()
    {
        Visible = true;
        for (int i = getCurrentMap()->Players.size() - 1; i >= 0; i--)
        {
            PlayerObject *player = getCurrentMap()->Players[i];

            if (Functions::InRange(getCurrentLocation(), player->getCurrentLocation(), Globals::DataRange))
            {
                CheckVisible(player, true);
                if (player->CheckStacked())
                {
                    player->StackingTime = getEnvir()->getTime() + 1000;
                    player->Stacking = true;
                }
            }
        }
    }

    bool NPCObject::FindNearby(int distance)
    {
        for (int d = 0; d <= distance; d++)
        {
            for (int y = getCurrentLocation()->Y - d; y <= getCurrentLocation()->Y + d; y++)
            {
                if (y < 0)
                {
                    continue;
                }
                if (y >= getCurrentMap()->Height)
                {
                    break;
                }

                for (int x = getCurrentLocation()->X - d; x <= getCurrentLocation()->X + d; x += std::abs(y - getCurrentLocation()->Y) == d ? 1 : d * 2)
                {
                    if (x < 0)
                    {
                        continue;
                    }
                    if (x >= getCurrentMap()->Width)
                    {
                        break;
                    }
                    if (!getCurrentMap()->ValidPoint(x, y))
                    {
                        continue;
                    }
                    Cell *cell = getCurrentMap()->GetCell(x, y);
                    if (cell->Objects.empty())
                    {
                        continue;
                    }

                    for (int i = 0; i < cell->Objects.size(); i++)
                    {
                        MapObject *ob = cell->Objects[i];
                        switch (ob->getRace())
                        {
                            case ObjectType::Player:
                                if (ob == this || ob->Dead)
                                {
                                    continue;
                                }
                                return true;
                            default:
                                continue;
                        }
                    }
                }
            }
        }

        return false;
    }

    Packet *NPCObject::GetInfo()
    {
        S::ObjectNPC *tempVar = new S::ObjectNPC();
        tempVar->ObjectID = ObjectID;
        tempVar->Name = getName();
        tempVar->NameColour = NameColour;
        tempVar->Image = Info->Image;
        tempVar->Colour = Info->Colour;
        tempVar->Location = getCurrentLocation();
        tempVar->Direction = getDirection();
        tempVar->QuestIDs = (from q in Quests select q::Index)->ToList();

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar' statement was not added since tempVar was used in a 'return' or 'throw' statement.
        return tempVar;
    }

    Packet *NPCObject::GetUpdateInfo()
    {
        S::NPCImageUpdate *tempVar = new S::NPCImageUpdate();
        tempVar->ObjectID = ObjectID;
        tempVar->Image = Info->Image;
        tempVar->Colour = Info->Colour;

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar' statement was not added since tempVar was used in a 'return' or 'throw' statement.
        return tempVar;
    }

    void NPCObject::ApplyPoison(Poison *p, MapObject *Caster, bool NoResist, bool ignoreDefence)
    {
        throw NotSupportedException();
    }

    std::string NPCObject::getName() const
    {
        return Info->Name;
    }

    void NPCObject::setName(const std::string &value)
    {
        throw NotSupportedException();
    }

    bool NPCObject::getBlocking() const
    {
        return Visible;
    }

    void NPCObject::CheckVisible(PlayerObject *Player, bool Force)
    {
        bool canSee;
        std::unordered_map<int, bool>::const_iterator VisibleLog_iterator = VisibleLog.find(Player->Info->Index);
        canSee = VisibleLog_iterator->second;

        if (Conq != nullptr && Conq->getWarIsOn())
        {
            if (canSee)
            {
                S::ObjectRemove *tempVar = new S::ObjectRemove(), *CurrentLocation, *Player;
                tempVar->ObjectID = ObjectID;
                getCurrentMap()->Broadcast(tempVar, CurrentLocation, Player);

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar' statement was not added since tempVar was passed to a method or constructor. Handle memory management manually.
            }
            VisibleLog[Player->Info->Index] = false;
            return;
        }

        if (Info->FlagNeeded != 0 && !Player->Info->Flags[Info->FlagNeeded])
        {
            if (canSee)
            {
                S::ObjectRemove *tempVar2 = new S::ObjectRemove(), *CurrentLocation, *Player;
                tempVar2->ObjectID = ObjectID;
                getCurrentMap()->Broadcast(tempVar2, CurrentLocation, Player);

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar2' statement was not added since tempVar2 was passed to a method or constructor. Handle memory management manually.
            }
            VisibleLog[Player->Info->Index] = false;
            return;
        }

        if (Info->MinLev != 0 && Player->getLevel() < Info->MinLev || Info->MaxLev != 0 && Player->getLevel() > Info->MaxLev)
        {
            if (canSee)
            {
                S::ObjectRemove *tempVar3 = new S::ObjectRemove(), *CurrentLocation, *Player;
                tempVar3->ObjectID = ObjectID;
                getCurrentMap()->Broadcast(tempVar3, CurrentLocation, Player);

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar3' statement was not added since tempVar3 was passed to a method or constructor. Handle memory management manually.
            }
            VisibleLog[Player->Info->Index] = false;
            return;
        }

//C# TO C++ CONVERTER TODO TASK: There is no C++ equivalent to 'ToString':
        if (Info->ClassRequired != "" && Player->getClass().ToString() != Info->ClassRequired)
        {
            if (canSee)
            {
                S::ObjectRemove *tempVar4 = new S::ObjectRemove(), *CurrentLocation, *Player;
                tempVar4->ObjectID = ObjectID;
                getCurrentMap()->Broadcast(tempVar4, CurrentLocation, Player);

//C# TO C++ CONVERTER TODO TASK: A 'delete tempVar4' statement was not added since tempVar4 was passed to a method or constructor. Handle memory management manually.
            }
            VisibleLog[Player->Info->Index] = false;
            return;
        }

        if (Visible && !canSee)
        {
            getCurrentMap()->Broadcast(GetInfo(), getCurrentLocation(), Player);
        }
        else if (Force && Visible)
        {
            getCurrentMap()->Broadcast(GetInfo(), getCurrentLocation(), Player);
        }

        VisibleLog[Player->Info->Index] = true;
    }

    int NPCObject::getCurrentMapIndex() const
    {
        return CurrentMapIndex;
    }

    void NPCObject::setCurrentMapIndex(int value)
    {
        CurrentMapIndex = value;
    }

    Point *NPCObject::getCurrentLocation() const
    {
        return Info->Location;
    }

    void NPCObject::setCurrentLocation(Point *value)
    {
        throw NotSupportedException();
    }

    MirDirection NPCObject::getDirection() const
    {
        return Direction;
    }

    void NPCObject::setDirection(MirDirection value)
    {
        Direction = value;
    }

    int NPCObject::getHealth() const
    {
        throw NotSupportedException();
    }

    int NPCObject::getMaxHealth() const
    {
        throw NotSupportedException();
    }

    int NPCSpeech::getWeight() const
    {
        return Weight;
    }

    void NPCSpeech::setWeight(int value)
    {
        Weight = value;
    }

    std::string NPCSpeech::getMessage() const
    {
        return Message;
    }

    void NPCSpeech::setMessage(const std::string &value)
    {
        Message = value;
    }

    int NPCSpeech::GetWeight(RandomProvider *rnd, int max)
    {
        return rnd->Next(getWeight(), max + 100);
    }
}
